javascript 理解函数调用

您所在的位置:网站首页 js 函数中的函数是什么 javascript 理解函数调用

javascript 理解函数调用

2024-07-15 13:06| 来源: 网络整理| 查看: 265

this参数:函数上下文

当调用函数时,除了显示提供的函数外,this参数也会默认地传递给函数。this参数是面向对象JavaScript编程的一个重要组成部分,代表函数调用相关联的对象。因此,通常称之为函数上下文。

函数上下文是来自面向对象语言(如java)的一个概念。在这些语言中,this通常指向定义在前面方法的类的实例。

但是要小心!正如接下来要提到的,在JavaScript中,将一个函数作为方法(method)调用仅仅是函数调用的一种方式。事实上,this参数指向不仅是由定义函数的方式和位置决定的,同时还严重受到函数调用方式的影响。真正理解this参数是面向对象JavaScript编程的基础,接下来我们将了解调用函数的不同方式。会发现它们之间的主要区别在于this值的不同。

函数调用

我们都调用过JavaScript函数,但你是否曾经想过一个问题被调用的时候真正发生了什么?事实上,函数调用方式对函数内代码的执行有很大的影响,主要体现在this参数以及函数上下文是如何建立的。这点尤为重要。

我们可以通过4种方式调用一个函数,每种方式之间有些细微差别。

1.作为一个函数(function)——skulk(),直接被调用。

2.作为一个方法(method)——ninja.skulk(),关联在一个对象上,实现面向对象编程。

3.作为一个构造函数(constructor)——new Ninja(),实例化一个新的对象。

4.通过函数的apply或者call方法——skulk.apply(ninja)或者skulk.call(ninja)。

console.log('--------------------------函数各种调用方式-------------------'); function skulk(name) {} function Ninja(name) {} //作为函数调用 skulk('Hattori'); (function (who) { return who;})('Hattori'); var ninja = { skulk: function () {} }; //作为ninja对象的一个方法调用 ninja.skulk('Hattori'); //作为构造函数调用 ninja = new Ninja('Hattori'); //通过call方法调用 skulk.call(ninja, 'Hattori'); //通过apply方法调用 skulk.apply(ninja, ['Hattori']);

 除了call和apply方法外,函数调用的操作符都是函数表达式之后加一对圆括号。

 

作为函数直接被调用

作为函数调用?听起来多么愚蠢,函数当然要被当作函数(function)调用。但实际上,这里我们所说的函数“作为一个函数”被调用是为了区别于其他的调用方式:方法、构造函数和apply/call。如果一个函数没有作为方法、构造函数或者通过apply和call调用的话,我们称为作为函数被直接调用。

通过()运算符调用一个函数,且被执行的函数表达式不是作为一个对象的属性存在时,就属于这种调用类型。(当执行函数的表达式是一个对象属性时,属于接下来将要讨论的方法调用类型)

//函数定义作为函数被调用

function ninja() {}

ninja();

//函数表达式作为函数被调用

var samurai = function () {

};

samurai();

//会被立即调用的函数表达式,作为函数被调用

(function() {

})()

当以这种方式调用时,函数上下文(this关键字的值)有两种可能性:在非严格模式下,它将全局上下文(window对象),在严格模式下,他将是undefined。

console.log('---------------函数调用的方式-------------'); //非严格模式下的函数 function ninjaThis() { return this; } //严格模式下的函数 function samurai() { 'use strict'; return this; } if (ninjaThis() === window) { console.log("In a 'nonstrict' ninja function, the context is the g;obal window object."); } if (samurai() === undefined) { console.log("In a 'strict' samuri function, the context is undefined."); }

 

注意:

很显然,在多数情况下,严格模式比非严格模式更简单易懂。例如,使用函数调用的方式(而不是作为方法被调用),并没有指定函数被调用的对象。因此,在我们看来,this关键字的确应该被设置为undefined(在严格模式下),而不应该是全局的window对象(在非严格模式下)。

一般而言,严格模式修复了很多JavaScript中类似的怪异表现。

当一个函数被赋值给一个对象的属性,并且通过对象属性引用的方式调用函数时,函数会作为对象的方法被调用。示例:

 

var ninja = {};

ninja.skulk = function () {

};

ninja.skulk();

这种情况下函数被称为方法,它有什么有趣或者不同之处吗?如果你有面向对象编程的经历,一定会联想到是否可以在方法内部通过this访问到对象主体。这种情况下同样适用。当函数作为某个对象的方法被调用时,该对象会成为函数的上下文,并且在函数内部可以通过参数(this)访问到。这也是JavaScript实现面向编程的主要方式之一。(构造函数是另外一种形式)

console.log('------------------------函数调用和方法调用的差别-----------------------'); //返回函数上下文,从而让我们从函数外面检查函数上下文。 function whatsMyContext() { return this; } //作为函数被调用并将其上下文设置为window对象。 if (whatsMyContext() === window) { console.log('Function call on window.'); } //变量getMyThis得到了函数whatsMyContext的引用。 var getMyThis = whatsMyContext; //使用变量getMyThis来调用函数,该函数仍作为函数被调用,函数上下文也依然是window对象。 if (getMyThis() === window) { console.log('Another function call in window.'); } //创建一个ninja1对象,其属性getMyThis得到了函数whatsMyContext的引用 var ninja1 = { getMyThis: whatsMyContext } //使用ninja1对象的方法getMyThis来调用函数。函数上下文现在是ninja1了。这就是面向对象。 if (ninja1.getMyThis() === ninja1) { console.log('Working with first ninja.'); } //创建一个对象ninja2,其属性getMyThis得到了函数whatsMyContext的引用 var ninja2 = { getMyThis: whatsMyContext } //使用ninja2对象的方法getMyThis来调用函数。函数上下文现在是ninja2 if (ninja2.getMyThis() === ninja2) { console.log('Working with second ninja'); }

 

这段代码中设置了一个名为whatsMyContext的函数,在整个过程中都将用到它。这个函数唯一的功能是返回它的函数上下文。这样就可以在函数外部看到调用的函数上下文。

//返回函数上下文,从而让我们从函数外面检查函数上下文。

function whatsMyContext() {

return this;

}

当直接通过函数名调用,也就是将函数作为函数调用时,因为是在非严格模式下执行,因此预期的函数上下文结果应当是全局上下文(window)。

//作为函数被调用并将其上下文设置为window对象。

if (whatsMyContext() === window) {

console.log('Function call on window.');

}

通过变量getMyThis创建了whatsMyContext函数的一个引用: var getMyThis = whatsMyContext;这样不会重复创建函数的实例,它仅仅是创建了原函数的一个引用(因为函数是第一类对象)。

因为函数调用操作符可以应用于任何表示函数的表达式中,所以可通过变量调用函数,这里再一次将函数作为函数调用。我们预期的函数上下文是window,方法如下:

//使用变量getMyThis来调用函数,该函数仍作为函数被调用,函数上下文也依然是window对象。

if (getMyThis() === window) {

console.log('Another function call in window.');

}

现在假设遇到了一些棘手的问题,需要定义一个名为ninja1的对象,并包含一个名为getMyThis的属性,属性的值为函数whatsMyContext的引用。这样我们就可以在对象上创建一个名为getMyThis的方法。不要认为whatsMyContext成为了ninja1的一个方法,whatsMyContext是一个独立的函数,他可以有多种调用方式。

//创建一个ninja1对象,其属性getMyThis得到了函数whatsMyContext的引用

var ninja1 = {

getMyThis: whatsMyContext

}

正如之前提到的,当通过方法引用调用函数时,我们期望的函数上下文是该方法所在的对象(在上述代码中表示ninja1)。

//使用ninja1对象的方法getMyThis来调用函数。函数上下文现在是ninja1了。这就是面向对象。

if (ninja1.getMyThis() === ninja1) {

console.log('Working with first ninja.');

}

注意:将函数作为方法调用对于实现JavaScript面向对象编程至关重要。这样你就可以通过this在任何方法中引用该方法的“宿主”对象——这也是面向对象编程的一个基本概念。

为了印证这一点,通过创建一个新的对象ninja2来进一步测试,它也包含一个引用了whatsMyContext函数的名为getMyThis的属性。当我们通过ninja2对象调研这个函数时,它的上下文对象即为ninja2;

//创建一个对象ninja2,其属性getMyThis得到了函数whatsMyContext的引用

var ninja2 = {

getMyThis: whatsMyContext

}

//使用ninja2对象的方法getMyThis来调用函数。函数上下文现在是ninja2

if (ninja2.getMyThis() === ninja2) {

console.log('Working with second ninja');

}

即使在整个示例中使用相同的函数——whatsMyContext,但通过this返回的函数上下文依然取决于whatsMyContext的调用方式。例如,ninja1和ninja2共享了完全相同的函数,但当执行函数时,该函数可以访问并操作所属对象的其他方法。因此我们不需要创建一个单独的函数副本来操作不同的对象进行相同的处理。这也是面向对象的魅力所在。

纵使功能强大,但其使用依然有一些限制。首先,我们通过创建两个ninja对象确实可以共享相同的函数以作为各自的方法,但是针对这些单独的对象和它们的方法,我们会使用到一些重复的代码。

 

参考《JavaScript忍者秘籍》

 

 

 

 

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3